def _go_template_impl(ctx):
  input = ctx.files.srcs
  output = ctx.outputs.out

  args = ["-o=%s" % output.path] + [f.path for f in input]

  ctx.actions.run(
      inputs = input,
      outputs = [output],
      mnemonic = "GoGenericsTemplate",
      progress_message = "Building Go template %s" % ctx.label,
      arguments = args,
      executable = ctx.executable._tool,
  )

  return struct(
      types = ctx.attr.types,
      opt_types = ctx.attr.opt_types,
      consts = ctx.attr.consts,
      opt_consts = ctx.attr.opt_consts,
      deps = ctx.attr.deps,
      file = output,
  )

"""
Generates a Go template from a set of Go files.

A Go template is similar to a go library, except that it has certain types that
can be replaced before usage. For example, one could define a templatized List
struct, whose elements are of type T, then instantiate that template for
T=segment, where "segment" is the concrete type.

Args:
  name: the name of the template.
  srcs: the list of source files that comprise the template.
  types: the list of generic types in the template that are required to be specified.
  opt_types: the list of generic types in the template that can but aren't required to be specified.
  consts: the list of constants in the template that are required to be specified.
  opt_consts: the list of constants in the template that can but aren't required to be specified.
  deps: the list of dependencies.
"""

go_template = rule(
    attrs = {
        "srcs": attr.label_list(
            mandatory = True,
            allow_files = True,
        ),
        "deps": attr.label_list(allow_files = True),
        "types": attr.string_list(),
        "opt_types": attr.string_list(),
        "consts": attr.string_list(),
        "opt_consts": attr.string_list(),
        "_tool": attr.label(
            executable = True,
            cfg = "host",
            default = Label("//tools/go_generics:go_merge"),
        ),
    },
    outputs = {
        "out": "%{name}_template.go",
    },
    implementation = _go_template_impl,
)

def _go_template_instance_impl(ctx):
  template = ctx.attr.template
  output = ctx.outputs.out

  # Check that all required types are defined.
  for t in template.types:
    if t not in ctx.attr.types:
      fail("Missing value for type %s in %s" % (t, ctx.attr.template.label))

  # Check that all defined types are expected by the template.
  for t in ctx.attr.types:
    if (t not in template.types) and (t not in template.opt_types):
      fail("Type %s it not a parameter to %s" % (t, ctx.attr.template.label))

  # Check that all required consts are defined.
  for t in template.consts:
    if t not in ctx.attr.consts:
      fail("Missing value for constant %s in %s" % (t, ctx.attr.template.label))

  # Check that all defined consts are expected by the template.
  for t in ctx.attr.consts:
    if (t not in template.consts) and (t not in template.opt_consts):
      fail("Const %s it not a parameter to %s" % (t, ctx.attr.template.label))

  # Build the argument list.
  args = ["-i=%s" % template.file.path, "-o=%s" % output.path]
  args += ["-p=%s" % ctx.attr.package]

  if len(ctx.attr.prefix) > 0:
    args += ["-prefix=%s" % ctx.attr.prefix]

  if len(ctx.attr.suffix) > 0:
    args += ["-suffix=%s" % ctx.attr.suffix]

  args += [("-t=%s=%s" % (p[0], p[1])) for p in ctx.attr.types.items()]
  args += [("-c=%s=%s" % (p[0], p[1])) for p in ctx.attr.consts.items()]
  args += [("-import=%s=%s" % (p[0], p[1])) for p in ctx.attr.imports.items()]

  ctx.actions.run(
      inputs = [template.file],
      outputs = [output],
      mnemonic = "GoGenericsInstance",
      progress_message = "Building Go template instance %s" % ctx.label,
      arguments = args,
      executable = ctx.executable._tool,
  )

  # TODO: How can we get the dependencies out?
  return struct(
      files = depset([output])
  )

"""
Instantiates a Go template by replacing all generic types with concrete ones.

Args:
  name: the name of the template instance.
  template: the label of the template to be instatiated.
  prefix: a prefix to be added to globals in the template.
  suffix: a suffix to be added to global in the template.
  types: the map from generic type names to concrete ones.
  consts: the map from constant names to their values.
  imports: the map from imports used in types/consts to their import paths.
  package: the name of the package the instantiated template will be compiled into.
"""

go_template_instance = rule(
    attrs = {
        "template": attr.label(
            mandatory = True,
            providers = ["types"],
        ),
        "prefix": attr.string(),
        "suffix": attr.string(),
        "types": attr.string_dict(),
        "consts": attr.string_dict(),
        "imports": attr.string_dict(),
        "package": attr.string(mandatory = True),
        "out": attr.output(mandatory = True),
        "_tool": attr.label(
            executable = True,
            cfg = "host",
            default = Label("//tools/go_generics"),
        ),
    },
    implementation = _go_template_instance_impl,
)
